<?php
/**
 * Pownce PHP Client Library
 * for the Pownce API
 * http://code.google.com/p/powncephplib/
 * View the example php file for some exampes of uses!
 * View the phpDoc for detailed function/method/obj explanations!
 * @author Jeff Hodsdon <onetrap@gmail.com> jeffhodsdon.com
 * @version  .02
 * @package Pownce
 * @link http://code.google.com/p/powncephplib/
 **/

define("POWNCE_API_BASE_URL" , "http://api.pownce.com");
define("POWNCE_API_VERSION" , "1.1");
define("POWNCE_API_URL" , POWNCE_API_BASE_URL . "/" . POWNCE_API_VERSION . "/");
define("DEFAULT_NUM_OF_NOTES" , 20); //By default how many note objs you want to return when you call something

class Pownce {

  protected $user;
  protected $debug;
  private $token;
  private $wsse_token;

  private $common_header = array("Host" => "pownce.com",
                            "User-Agent" => "PHP5",
                            "Content-Type" => "application/x-www-urlencoded",
                            "Connection" => "close");

  /**
   * Constructor
   *
   * @param string $user The user to open the client for
   * @param string $password Optional: The password for that user
   * @param bool $debug Optional: Debug on or off
   */
  public function __construct($user, $password = null, $debug)
  {
    $this->user = $user;
    $this->debug = $debug;

    if (isset($password))
    {
      try
      {
        $this->token = $this->auth($this->user, $password);

        $nonce = md5(time() . rand());
        $time = date("Y-m-d\TG:i:s\Z");
        $pass_digest = base64_encode(sha1($nonce . $time . $this->token, true));

        $this->wsse_token = urlencode("UsernameToken Username=\""
                                      . $this->user . "\", PasswordDigest=\""
                                      . $pass_digest . "\", Nonce=\""
                                      . $nonce . "\", Created=\""
                                      . $time . "\"");
      }
      catch (Exception $e)
      {
        throw new PownceException();
      }
    }
  }

  /**
   * Get XML data
   * Gathers XML data from a url
   *
   * @param string $url
   * @return string String of XML to be parsed
   */
  protected function xml_data($url)
  {
    $url = parse_url($url);
    $headers = $this->common_header;
    $headers['Host'] = "api.pownce.com";
    $data = $this->http_request("GET", $url['path'] . "?" .$url['query'], $headers);
    $data = strstr($data, "\r\n\r\n"); //TODO There must be a better way
    return trim($data);
  }

  /**
   * Auth a user by getting their login token
   *
   * @param string $username
   * @param string $password
   * @return string The users login token
   */
  private function auth($username, $password)
  {
    $auth = base64_encode($username . ":" . $password);

    $headers = $this->common_header;
    $headers['Authorization'] = "basic " . $auth;

    $response = $this->http_request("GET", "/api/login/", $headers, null);

    $xml_string = $this->trim_header($response);
    $xml_obj = simplexml_load_string($xml_string);
    $xml_array = $this->sxml_to_array($xml_obj);


    return $xml_obj['token'];
  }

  /**
   * Send Message
   *
   * @param string $to Must be "public", "all"(all the persons friends), "set_xx"(A group set, where xx is the number), "friend_yyy"(To a friend, yyy is there uid)
   * @param string $body The body of the note
   * @return xml Success xml, which is the note
   */
  public function send_message($to, $body)
  {
    return $this->send_note("note-message", $to, $body);
  }
  /**
   * Send Link
   *
   * @param string $to Must be "public", "all"(all the persons friends), "set_xx"(A group set, where xx is the number), "friend_yyy"(To a friend, yyy is there uid)
   * @param string $body The body of the link
   * @param string $link The link you'd like to send
   * @return xml Success xml, which is the note
   */
  public function send_link($to, $body, $link)
  {
    $params = array("url" => $link);
    return $this->send_note("note-link", $to, $body, $params);
  }

  /**
   * Send Event ( Doesnt work? :( )
   *
   * @param string $to Must be "public", "all"(all the persons friends), "set_xx"(A group set, where xx is the number), "friend_yyy"(To a friend, yyy is there uid)
   * @param string $body The body of the event
   * @param string $event_name Name of the event
   * @param string $event_location Location of the event
   * @param int $event_date_day The day of the event IE: 4
   * @param int $event_date_month The month of the event IE: 12
   * @param int $event_date_year The year of the event IE: 2007
   * @param string $event_time The time of the event IE: 9:00 PM
   * @return xml Success xml, which is the event
   */
  public function send_event($to, $body, $event_name, $event_location, $event_date_day, $event_date_month, $event_date_year, $event_time)
  {
    $params = array("event_date_day" => $event_date_day,
                    "event_name" => $event_name,
                    "event_date_month" => $event_date_month,
                    "event_date_year" => $event_date_year,
                    "event_location" => $event_location,
                    "event_date_time" => $event_time
                    );
    return $this->send_note("note-event", $to, $body, $params);
  }

  public function send_file($to, $body, $file)
  {
    //TODO This...
  }

  /**
   * Send Note
   *
   * @param string $type Must be "note-event", "note-message", "note-link", "note-file"
   * @param string $to Must be "public", "all"(all the persons friends), "set_xx"(A group set, where xx is the number), "friend_yyy"(To a friend, yyy is there uid)
   * @param string $body The body of the note
   * @param array $opt_post_data Optional: This in an array of the optional post vars.  By key and value.
   * @return xml Success xml, which is the note
   */
  public function send_note($type, $to, $body, $opt_post_data = array())
  {
    $post_data = array("note_body" => $body,
                     "note_type" => $type,
                     "note_to" => $to);
    $params = array_merge($post_data, $opt_post_data);
    return $this->http_request("POST", "/api/notes/", $this->common_header, $params);
  }

  /**
   * Http Request
   *
   * @param string $method What method, POST or GET
   * @param string $path The path to send a request too
   * @param array $headers An array of the headers
   * @param array $params Optional:  Array of post params, by key and value.
   * @return string The raw response of the request
   */
  protected function http_request($method, $path, $headers = array(), $params = null)
  {
    $fp = fsockopen('pownce.com', 80, $errno, $errstr);
    if (!$fp)
    {
      echo $errno;
    }
    else {
      if(isset($params))
      {
        foreach ($params as $key => $val) {
          $post_params[] = $key.'='.urlencode($val);
        }

        $post_params[] = "auth=" . $this->wsse_token;
        $post_string = implode('&', $post_params);
        $headers['Content-length'] = strlen($post_string);
      }

      $request = strtoupper($method) . " " . $path . " " . "HTTP/1.1\r\n";
      foreach ($headers as $name => $value)
      {
        $request .= $name . ": " . $value . "\r\n";
      }
      $request .= "\r\n";
      if(isset($params)) $request .= $post_string . "\r\n";

    }

    fputs($fp, $request);

    $response = "";
    while (!feof($fp))
    {
      $response .= fgets($fp, 128);
    }

    if ($this->debug)
    {
      echo "<span style=\"background:#666; font-size: small;\">
        HTTP Request: \n<br>" . $request . "
        HTTP Response: \n<br>" . $response . "</span>";
    }
    fclose($fp);

    return $response;
  }

  protected function trim_header($response)
  {
    $response = explode("\r\n\r\n", $response);
    return $response[1];
  }

  /**
   * Simple XML to array
   *
   * @param object $sxml
   * @return array
   */
  protected function sxml_to_array($sxml)
  {
    $xml_array = array();

    foreach ($sxml as $key => $val)
    {
      if (count($val) > 1 || $key == "sender" || $key == "link")
      {
        $xml_array[$key] = $this->sxml_to_array($val);
      }
      else {
        $xml_array[$key] = (string) $val;
      }
    }
    return $xml_array;
  }

  public function logout()
  {
    if (isset($this->token))
    {
      return $this->http_request("POST", "/api/logout/");
    }
    $this->user = null;
    $this->token = null;
    $this->wsse_token = null;
  }

  function __destruct()
  {
    //$this->logout();
  }

}

/**
 * Pownce API XML Client
 * @subpackage  PownceXML
 */
class PownceXML extends Pownce {

  /**
   * Constructor
   * This is where you can define a user to open the client up for, they will be the default
   * person for each particular function/method
   * @param string $user
   * @param bool $debug
   */
  public function __construct($user, $password = null, $debug = false)
  {
    parent::__construct($user, $password, $debug);
  }

  /**
   * Get Public links
   * Get an array of public links
   * @param int $limit Optional: The number of links you want to return
   * @return array An array of simplexml objects
   */
  public function get_public_links($limit = null)
  {
    return $this->get_public_notes($limit, 'links');
  }

  /**
   * Get Public messages
   * Get an array of public messages
   * @param int $limit Optional: The number of messages you want
   * @return array An array of simple xml objects
   */
  public function get_public_messages($limit = null)
  {
    return $this->get_public_notes($limit, 'messages');
  }

  /**
   * Get public events
   * Get an array of public events
   * @param int $limit Optional: the number of events you want
   * @return array An array of simple xml objects
   */
  public function get_public_events($limit = null)
  {
    return $this->get_public_notes($limit, 'events');
  }

  /**
   * Get username
   * Get the persons user name that the client is opened for
   * @param string $person Optional:  A particular person you want to get the username for
   * @return string The persons username
   */
  public function get_username()
  {
    return $this->user;
  }

  /**
   * Get permalink
   * Get a persons permalink
   * @param string $person Optional: A particular person you want to get the username for
   * @return string
   */
  public function get_permalink($person = null)
  {
    return $this->get_profile_field('permalink', $person);
  }

  /**
   * Get frist name
   * Get a persons first name
   * @param string $person Optional
   * @return string
   */
  public function get_first_name($person = null)
  {
    return $this->get_profile_field('first_name', $person);
  }

  /**
   * Get short name
   * Get a persons short name
   * @param string $person Optional
   * @return string
   */
  public function get_short_name($person = null)
  {
    return $this->get_profile_field('short_name', $person);
  }

  /**
   * Is pro
   * Boolean for if a person has a pro account
   * @param string $person Optional
   * @return bool
   */
  public function is_pro($person = null)
  {
    if ($this->get_profile_field('is_pro', $person) == 1)
    {
    	return true;
    }
    else {
      return false;
    }
  }

  /**
   * Get blurb
   * Get a person's blurb
   * @param string $person Optional
   * @return string
   */
  public function get_blurb($person = null)
  {
    return $this->get_profile_field('blurb', $person);
  }

  /**
   * Get location
   * Get a person's location
   * @param string $person Optional
   * @return string
   */
  public function get_location($person = null)
  {
    return $this->get_profile_field('location', $person);
  }

  /**
   * Get country
   * Get a persons country
   * @param string $person Optional
   * @return string
   */
  public function get_country($person = null)
  {
    return $this->get_profile_field('country', $person);
  }

  /**
   * Get gender
   * Get a person's gender
   * @param string $person Optional
   * @return string
   */
  public function get_gender($person = null)
  {
    return $this->get_profile_field('gender', $person);
  }

  /**
   * Get age
   * Get a person's age
   * @param string $person Optional
   * @return int
   */
  public function get_age($person = null)
  {
    return $this->get_profile_field('age', $person);
  }

  /**
   * Get profile data
   * Get a persons whole profile
   * @param string $field The field of info, EX: first_name, blurb, age, etc.
   * @param string $person Optional:  Default is the person that the client is opened for
   * @return string || array Since there are simplexml obj's inside a persons profile it can return both
   */
  private function get_profile_field($field, $person = null)
  {
    if (!isset($person)) $person = $this->user;
    $data = $this->get_profile($person);
    return $data[$field];
  }

  /**
   * Get Photo
   * Get a persons photo url, depending on which size you want
   * @param string $size Optional: Deafult is medium.  Choose from "tiny", "small", "smedium", "medium", "large"
   * @param string $person Optional: Deafult is the person the client is opened for.
   * @return string Image url
   */
  public function get_photo($size = 'medium', $person = null)
  {
    if (!isset($person)) $person = $this->user;
    $profile = $this->get_profile($person);
    return $profile['profile_photo_urls'][$size];
  }

  /**
   * Person's note count
   * @param string $person Optional:  Who's notes you want to count
   * @return int
   */
  public function note_count($person = null)
  {
    return count($this->get_persons_public_notes_from($person, null, 9999999999));
  }

  /**
   * Person's message count
   * @param string $person Optional: Who's messages you want to count
   * @return int
   */
  public function message_count($person = null)
  {
    return count($this->get_persons_public_messages('from', $person, 9999999999));
  }

  /**
   * Person's link count
   * @param string $person Optional: Who's links you want to count
   * @return int
   */
  public function link_count($person = null)
  {
    return count($this->get_persons_public_links('from', $person, 9999999999));
  }

  /**
   * Person's event count
   * @param string $person Optional: Who's events you want to count
   * @return int
   */
  public function event_count($person = null)
  {
    return count($this->get_persons_public_events('from', $person, 9999999999));
  }

  /**
   * Person's friend count
   * @return int
   */
  public function friend_count($person = null)
  {
    return count($this->friend_obj_array($person));
  }

  /**
   * Person's fan count
   * @return int
   */
  public function fan_count($person = null)
  {
    return count($this->fan_obj_array($person));
  }

  /**
   * Person's fan_of count
   * @return int
   */
  public function fan_of_count($person = null)
  {
    return count($this->fan_of_obj_array($person));
  }

  /**
   *  Person's friends obj array.
   * Get an array of a person's friends
   * @param string $person Optional:  Which person to get the friends of
   * @param string $field Optional: Which field you are to get an array of, ex: "username", "first_name"
   * @return array
   **/
  public function friend_obj_array($person = null, $field = null)
  {
    return $this->info_obj_array("friends", $person, $field);
  }

  /**
   * Person's fans obj array
   * Get an array of person's fans
   * @param string $person Optional:  Which person to get the fans of
   * @return array
   */
  public function fan_obj_array($person = null, $field = null)
  {
    return $this->info_obj_array("fans", $person, $field);
  }

  /**
   * Person's fan_of obj array
   * Get an array of the people that are fans of a person
   * @param string $person Optional:  Which person who you want to find out which people they are fans of
   * @return  array
   */
  public function fan_of_obj_array($person = null, $field = null)
  {
    return $this->info_obj_array("fan_of", $person, $field);
  }


  /**
   * Is friend
   * Boolean test to see if 2 people are friends
   * @param string $person The person to test
   * @param string $person2 Optional: If you dont want to test the person the client is opened for
   * @return bool
   */
  public function is_friend($person, $person2 = null)
  {
    return $this->is_relation($person, $person2, "friends");
  }

  /**
   * Is fan, True if fan, false if not
   * @param string $person The person to test
   * @param string $person2 Optional: If you dont want to test the person the client is opened for
   * @return bool
   */
  public function is_fan($person, $person2 = null)
  {
    return $this->is_relation($person, $person2, "fans");
  }

  /**
   * Is fan_of, True if they are a fan of, False if not
   * @param string $person Person to test
   * @param string $person2 Optional: If you dont want to test the person the client is opened for
   * @return bool
   */
  public function is_fan_of($person, $person2 = null)
  {
    return $this->is_relation($person, $person2, "fan_of");
  }

  /**
   * Get notes from
   * Get an array of notes sent by a person, you may specify how many to get
   * @param string $person Optional:  The person to get the notes from
   * @param string $type Optional: The type of note, messages, links, or events
   * @param int $limit Optional: How many notes you want, if there is that many
   * @return array An array of simplexml obj for the notes
   */
  public function get_persons_public_notes_from($person = null, $type = null, $limit = null)
  {
    return $this->get_persons_public_notes('from', $person, $type, $limit);
  }

  /**
   * Get notes to
   * Get an array of notes sent to a person, you may specify how many to get, and which kind
   * @param string $person Optional:  The person to get the notes from
   * @param string $type Optional: The type of note, messages, links, or events
   * @param int $limit Optional: How many notes you want, if there is that many
   * @return array An array of simplexml obj for the notes
   */
  public function get_persons_public_notes_to($person = null, $type = null, $limit = null)
  {
    return $this->get_persons_public_notes('to', $person, $type, $limit);
  }

  /**
   * Get notes for
   * Get an array of all notes sent to and by a person, you may specify how many to get, and which kind
   * @param string $person Optional:  The person to get the notes from
   * @param string $type Optional: The type of note, messages, links, or events
   * @param int $limit Optional: How many notes you want, if there is that many
   * @return array An array of simplexml obj for the notes
   */
  public function get_persons_public_notes_for($person = null, $type = null, $limit = null)
  {
    return $this->get_persons_public_notes('for', $person, $type, $limit);
  }

  /**
   * Get persons public messages
   * Get an array of all person's public messages, you may specify how many to get
   * @param string $rel The relation the messages have to the person have, either "from", "to", "for"(both sent and to)
   * @param string $person Optional: The person to get the messages from
   * @param int $limit Optional
   * @return array An array of messages
   */
  public function get_persons_public_messages($rel, $person = null, $limit = DEFAULT_NUM_OF_NOTES)
  {
    return $this->get_persons_public_notes($rel, $person, 'messages', $limit);
  }

  /**
   * Get persons public links
   * Get an array of all person's public links, you may specify how many to get
   * @param string $rel The relation the links have to the person have, either "sent", "to", "all"(both sent and to)
   * @param string $person Optional: The person to get the links from
   * @param int $limit Optional
   * @return array An array of links
   */
  public function get_persons_public_links($rel, $person = null, $limit = DEFAULT_NUM_OF_NOTES)
  {
    return $this->get_persons_public_notes($rel, $person, 'links', $limit);
  }

  /**
   * Get persons public events
   * Get an array of all person's public events, you may specify how many to get
   * @param string $rel The relation the events have to the person have, either "sent", "to", "all"(both sent and to)
   * @param string $person Optional: The person to get the events from
   * @param int $limit Optional
   * @return array An array of events
   */
  public function get_persons_public_events($rel, $person = null, $limit = DEFAULT_NUM_OF_NOTES)
  {
    return $this->get_persons_public_notes($rel, $person, 'events', $limit);
  }

  /***Utility functions****/

  /**
   * Is relation, tests a relation to the user.  Returns true if they are.
   * @param string $person The person that you want to check the relation for
   * @param string $person2 Optional: if you want to test someone else, rather than who the client is opened for
   * @param string $relation What relation to check.  Either friends, fans, fan_of
   * @param string $name_type Optional: Either "username", "first_name", "short_name"
   * Default is "username"
   * @return  bool
   */
  private function is_relation($person, $person2 = null, $relation, $name_type = "username")
  {
    if(!isset( $person2)) $person2 = $this->user;

    if ($relation == "friend") $relation = "friends";
    if ($relation == "fan") $relation = "fans";

    $person_array = $this->info_obj_array($relation,  $person2, $name_type);

    return in_array($person, $person_array);
  }

  /**
   * Get a person's profile.
   * Get person's profile data
   * @param string $person Optional: Default is whoever the client is opened for
   * @return array Profile obj data
   */
  private function get_profile($person = null)
  {
    if (!isset($person)) $person = $this->user;

    $xml_url = POWNCE_API_URL . "users/" . $person . ".xml";
    $xml_file = parent::xml_data($xml_url);
    $xml_obj = simplexml_load_string($xml_file);
    $xml_array = $this->sxml_to_array($xml_obj);

    return $xml_array;
  }

  /**
   * Array of relation information, returns an array of particular infomation of people of certain relationship to that user.
   * @param string $relation Either "friends", "fans", or "fan_of"
   * @param string $person Optional: If you want to get data from an other person than the client is opened for
   * @param string $field Optional: Ex: username, blurb, etc
   * @return array
   */
  private function info_obj_array($relation, $person = null, $field = "notset")
  {
    if (!isset($person)) $person = $this->user;
    $person_array = array();

    for($i = 0; ; $i++)
    {
      //Currently the api has a max of 100 listings per xml doc
      $xml_url = POWNCE_API_URL . "users/" . $person . "/" . $relation . ".xml?limit=100&page=" . $i;
      $xml_file = parent::xml_data($xml_url);
      $xml_obj = simplexml_load_string($xml_file) or die("Could not loadd XML from API, sorry :(");

      if (count($xml_obj->user) > 0)
      {
        foreach ($xml_obj->user as $user_obj)
        {
          static $curr_person = 0;
          $curr_person++;

          if ($field != "notset")
          {
          	$person_array[$curr_person] = $user_obj->$field;
          }
          else {
            $person_array[$curr_person] = $user_obj;
          }
        }
      }
      else {
        break;
      }
    }
    $xml_array = $this->sxml_to_array($person_array);
    return $xml_array;
  }

  /**
   * Get Person's public notes
   * Gather a persons public notes, you may specify the relation of the note, whoms notes, the type, and how many you want.
   *
   * @param string $rel The relation, either "for", "from", or "to"
   * @param string $person Optional: The person you want to get notes from/to or whatevs
   * @param string $type Optional: The type of note "messages", "links", or "events"
   * @param string $limit Optional: Default is 20  How many notes you want
   * @return array An array of simple xml obj's
   */
  private function get_persons_public_notes($rel, $person = null, $type = null, $limit = DEFAULT_NUM_OF_NOTES)
  {
    if (!isset($person)) $person = $this->user;

    if ($limit <= 100)
    {
      $page_limit = $limit;
    }
    else {
      $page_limit = 100;
    }
    $note_array = array();

    for($i = 0; ; $i++)
    {
      //Currently the api has a max of 100 listings per xml doc
      $xml_url = POWNCE_API_URL . "public_note_lists/" . $rel . "/" . $person . ".xml?limit=" . $page_limit . "&type=" . $type ."&page=" . $i;
      $xml_file = $this->xml_data($xml_url);
      $xml_obj = simplexml_load_string($xml_file) or die("Could not load XML from API, sorry :(");
      if (count($xml_obj->note) > 0 && count($note_array) < $limit)
      {
        foreach ($xml_obj->note as $note_obj)
        {
          if (count($note_array) < $page_limit)
          {
            $curr_note = count($note_array);
            $note_array[$curr_note] = $note_obj;
          }
          else {
            break;
          }
        }
        if (count($note_array) == $limit) break;
      }
      else {
        break;
      }
    }

    $xml_array = $this->sxml_to_array($note_array);
    return $xml_array;
  }

  /**
   * Get public notes
   * Gets most recent public notes, you may specify only a certain type aswell as a the number or limit you want.
   *
   * @param string $limit The number of how many to show, if there are that many. 1 and up
   * @param string $type The trype of notes, "messages", "links", or "events"
   * @return array An array of simple xml obj's
   */
  private function get_public_notes($limit = DEFAULT_NUM_OF_NOTES, $type = null)
  {
    if ($limit <= 100)
    {
      $page_limit = $limit;
    }
    else {
        $page_limit = 100;
    }

    $note_array = array();
    for($i = 0; ; $i++)
    {
      //Currently the api has a max of 100 listings per xml doc
      $xml_url = POWNCE_API_URL . "public_note_lists.xml?limit=" . $page_limit . "&type=" . $type ."&page=" . $i;
      $xml_file = parent::xml_data($xml_url);
      $xml_obj = simplexml_load_string($xml_file) or die("Could not load XML from API, sorry :(");

      if (count($xml_obj->note) > 0 && count($note_array) < $limit)
      {
        foreach ($xml_obj->note as $note_obj)
        {
          if (count($note_array) < $limit)
          {
            static $curr_note = 0;
            $curr_note++;
            $note_array[$curr_note] = $note_obj;
          }
          else {
            break;
          }
        }
        if (count($note_array) == $limit) break;
      }
      else {
        break;
      }
    }

    $xml_array = $this->sxml_to_array($note_array);
    return $xml_array;
  }

}

/**
 * Pownce API JSON Client
 * @subpackage  PownceJSON
 *
 */
class PownceJSON extends Pownce {

}

class PownceException extends Exception {

}

?>